/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include "builder.h"
#include "atmosphere.h"
#include "exception.h"
#include <sstream>

#ifdef WIN32
	#include <assert.h>
#endif

using namespace performance;

////////////////////////////////////////////////////////////////////////////////
//
// performance::Builder
//
//------------------------------------------------------------------------------
//
//

/*!
 *
 */
Builder::Builder() : _data( new Data ), _pdo( 0 ), _atmos( 0 ) {}

/*!
 *
 */
Builder::~Builder() {}


//------------------------------------------------------------------------------
//
//

/*!
 *
 */
void Builder::build_data( int id ) {
	_pdo = new PDO();
	_pdo->classNo = id;
}

/*!
 *
 */
void Builder::build_type( 
		const std::string &id
	  , const std::string &wtc
	  , int syd, int bne
	  , int performance 
) {
	PDT *ac_type = new PDT;
	ac_type->type = std::string( id );
	ac_type->wtc = get_cat( wtc );
	ac_type->syMSC = syd;
	ac_type->bnMSC = bne;
	ac_type->classRef = performance;

	std::string key = id;
	if ( 0 != _data->_type.count( key ) ) {
		key.append( 
				L == ac_type->wtc ? "L"
			  : M == ac_type->wtc ? "M"
			  :                     "H"
			);
	}

	_data->_type.insert( std::make_pair( key, ac_type ) );
}


/*!
 *
 */
void Builder::build_holding( double lower, double middle, double upper ) {
	_pdo->lowHold = lower;
	_pdo->midHold = middle;
	_pdo->uppHold = upper;
}

/*!
 *
 */
void Builder::build_takeoff( 
		double speed, double duration, double altitude, double distance 
) {
	_pdo->toSpeed = speed;
	_pdo->toDuration = duration;
	_pdo->toAltitude = altitude;
	_pdo->toDistance = distance / 10.0;
}

/*!
 *
 */
void Builder::build_landing( double speed ) {
	_pdo->finSpeed = speed;
}

/*!
 *
 */
void Builder::build_xsection( double xsect) {
	_pdo->xSection = xsect;
}

/*!
 *
 */
void Builder::build_maximums( 
		double speed, double altitude, double turn, bool mach, bool jet 
) {
	_pdo->maxSpeed = speed;
	_pdo->maxAltitude = altitude * 100.0;
	_pdo->maxTurnSp = turn / 10.0;
	_pdo->is_mach = mach;
	_pdo->is_jet = jet;
}

/*!
 *
 */
void Builder::build_turn_speed( double speed ) {
	_pdo->stdTurnSp = speed / 10.0;
}

/*!
 *
 */
void Builder::build_turn_rates( 
		double lower, double middle, double upper 
) {
	_pdo->redTurn = lower / 100.0;
	_pdo->accTurn = middle / 100.0;
	_pdo->maxTurn = upper / 100.0;
}

/*!
 *
 */
void Builder::build_acceleration( 
		double lower, double middle, double upper 
) {
	_pdo->redAccel = lower / 100.0;
	_pdo->accAccel = middle / 100.0;
	_pdo->maxAccel = upper / 100.0;
}

/*!
 *
 */
void Builder::build_decelerations( 
		double lower, double middle, double upper 
) {
	_pdo->redDecel = lower / 100.0;
	_pdo->accDecel = middle / 100.0;
	_pdo->maxDecel = upper / 100.0;
}

/*!
 *
 */
void Builder::build_climb_rates( 
		double lower, double middle, double upper 
) {
	_pdo->redClimb = lower / 100.0;
	_pdo->accClimb = middle / 100.0;
	_pdo->maxClimb = upper / 100.0;
}

/*!
 *
 */
void Builder::build_descend_rates( 
		double lower, double middle, double upper 
) {
	_pdo->redDescend = lower / 100.0;
	_pdo->accDescend = middle / 100.0;
	_pdo->maxDescend = upper / 100.0;
}

/*!
 *
 */
void Builder::build_level_count( int lvls) {
	_pdo->levelData = new std::vector< AltitudeData >( lvls );
}

/*!
 *
 */
void Builder::build_altitude_levels( 
		const std::vector< double > &values 
) {
	std::vector<double> levelsFt;

	std::vector<double>::const_iterator ait = values.begin();
	for ( ; ait != values.end(); ++ait ) 
			levelsFt.push_back( (*ait) * 100 );

	for ( int i = 0; i < levelsFt.size(); ++i ) 
			_pdo->levelData->operator[]( i ).level = levelsFt[i];

	generate_atmospheric_params( levelsFt );

	build_maximum_speed_levels();
}

/*!
 *
 */
void Builder::build_climb_rate_levels( 
		const std::vector< double > &values 
) {
	for ( int i = 0; i < values.size(); ++i ) 
			_pdo->levelData->operator[]( i ).climbRate = values[i];
}

/*!
 *
 */
void Builder::build_descend_rate_levels( 
		const std::vector< double > &values 
) {
	for ( int i = 0; i < values.size(); ++i ) 
			_pdo->levelData->operator[]( i ).descendRate = values[i];
}

/*!
 *
 */
void Builder::build_acceleration_levels( 
		const std::vector< double > &values 
) {
	for ( int i = 0; i < values.size(); ++i ) 
			_pdo->levelData->operator[]( i ).acceleration = values[i];
}

/*!
 *
 */
void Builder::build_deceleration_levels( 
		const std::vector< double > &values 
) {
	for ( int i = 0; i < values.size(); ++i ) 
			_pdo->levelData->operator[]( i ).deceleration = values[i];
}

/*!
 *
 */
void Builder::build_cruising_speed_levels( 
		const std::vector< std::string > &values 
) {
	std::vector< double > velocities 
			= construct_level_velocities( values );
	for ( int i = 0; i < velocities.size(); ++i ) 
			_pdo->levelData->operator[]( i ).cruiseSpeed = velocities[i];
}

/*!
 *
 */
void Builder::build_stall_speed_levels( 
		const std::vector< std::string > &values 
) {
	std::vector< double > velocities 
			= construct_level_velocities( values );
	for ( int i = 0; i < velocities.size(); ++i ) 
			_pdo->levelData->operator[]( i ).stallSpeed = velocities[i];
}

/*!
 *
 */
void Builder::build_ascent_speed_levels( 
		const std::vector< std::string > &values 
) {
	std::vector< double > velocities 
			= construct_level_velocities( values );
	for ( int i = 0; i < velocities.size(); ++i ) 
			_pdo->levelData->operator[]( i ).climbSpeed = velocities[i];
}

/*!
 *
 */
void Builder::build_descent_speed_levels( 
		const std::vector< std::string > &values 
) {
	std::vector< double > velocities 
			= construct_level_velocities( values );
	for ( int i = 0; i < velocities.size(); ++i ) 
			_pdo->levelData->operator[]( i ).descendSpeed = velocities[i];
}

/*!
 *
 */
void Builder::build_sidstar( const std::string &sid ) {
	_pdo->sidStar = get_cat( sid );
}

/*!
 *
 */
void Builder::accept_performance() {
	_data->_performance.insert( std::make_pair( _pdo->classNo, _pdo ) );
	_pdo = 0;
	delete _atmos;
}


//------------------------------------------------------------------------------
// [private] implementation
//

/*!
 *
 */
LHM_CAT Builder::get_cat( const std::string &cat ) {
	return  ! cat.compare( "L" ) ? L 
		 :  ! cat.compare( "M" ) ? H
		 :                         M;
}

/*!
 *
 */
std::vector< double > Builder::construct_level_velocities( 
		const std::vector< std::string > &values 
) {
	std::vector< double > velocities;

	for ( int i = 0; i < values.size(); ++i ) {
		std::istringstream is( values[i] );
		char flg;
		double val;
		is >> flg >> val;
		velocities.push_back( convert2tas( i, flg, val ) );
	}

	return velocities;
}

/*!
 *
 */
void Builder::generate_atmospheric_params( 
		const std::vector< double > &levels 
) {
	_atmos = new Atmosphere( levels );
}

/*!
 *
 */
void Builder::build_maximum_speed_levels() {
	AltData *altd = _pdo->levelData;
	for ( int i = 0; i < altd->size(); ++i ) {
		(*altd)[i].maximumSpeed 
				= _atmos->cas2tas_max( i, _pdo->maxSpeed, _pdo->is_mach ); 
	}
}

/*!
 * Convert to true airspeed
 *
 * @param level needed for atmospheric parameter lookup
 * @param flag indicates either Mach or (N) corrected airspeed
 * @param value speed
 */
double Builder::convert2tas( int level, const char flag, double value ) {
	double retval;

	if ( 'N' == flag ) {
		retval = _atmos->cas2tas( level, value );
	}
	else if ( 'M' == flag ) {
		retval = _atmos->mach2tas( level, value / 100.0 );
	}
	else {
		std::ostringstream os;
		os
			 << "parser::invalid airspeed type [ " 
			 << flag << ":" << value 
			 << " ] at level [ "
			 << level << " ]";
		throw performance_data_error( os.str() );
	}

	return retval;
}

////////////////////////////////////////////////////////////////////////////////
